import { BasePageRequestDto, BasePageResponseDto, BaseSingleResponseDto } from "../dtos/BaseDto";
import { CreateUserDto, UpdateUserDto, UserDto } from "../dtos/UserDto";
import { User } from "../entity/User";
import { Service } from "typedi";
import { In, Repository, getRepository } from "typeorm";
import { InjectRepository } from "typeorm-typedi-extensions";
import { transformResponseDto } from "../utils/transform";
import { Role } from "../entity/Role";
import { RoleDto } from "../dtos/RoleDto";
import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from "../constants/request";

@Service()
export class UserService {
  @InjectRepository(User)
  private userRepo!: Repository<User>;

  @InjectRepository(Role)
  private roleRepo!: Repository<Role>;

  public async findAll(query: BasePageRequestDto): Promise<BasePageResponseDto<UserDto>> {
    const { pageIndex = DEFAULT_PAGE_INDEX, pageSize = DEFAULT_PAGE_SIZE } = query;
    const [users, count] = await this.userRepo.createQueryBuilder('user')
      .leftJoinAndSelect('user.roles', 'role')
      .skip((pageIndex - 1) * pageSize)
      .take(pageSize)
      .getManyAndCount();
    const userDtos = users.map(item => {
      return this.transformEntity2Dto(item);
    })
    return {
      success: true,
      message: 'Get users',
      data: userDtos,
      total: count,
      pageIndex,
      pageSize,
    };
  }

  public async get(id: number, message = 'get user success'): Promise<BaseSingleResponseDto<UserDto>> {
    const user = await this.userRepo.findOne({ where: { id }, relations: ['roles'] });
    if (!user) {
      throw new Error('user not found');
    }
    const resUser = this.transformEntity2Dto(user!);
    return {
      success: true,
      message: message,
      data: resUser,
    }
  }

  public async create(user: CreateUserDto): Promise<BaseSingleResponseDto<UserDto>> {
    const roles = await this.roleRepo.findBy({ id: In(user.roleIds || []) });
    const newUser = this.userRepo.create({
      username: user.username,
      email: user.email,
      firstName: user.firstName,
      lastName: user.lastName,
      password: user.password,
      roles,
    });
    await this.userRepo.save(newUser);
    const resUser = this.transformEntity2Dto(newUser);
    return {
      success: true,
      message: 'User created',
      data: resUser,
    };
  }

  public async update(user: UpdateUserDto): Promise<BaseSingleResponseDto<UserDto>> {
    const oldUser = await this.userRepo.findOne({ where: { id: user.id } });
    if (!oldUser) {
      throw new Error('user not found');
    }
    const roles = await this.roleRepo.findBy({ id: In(user.roleIds || []) });
    const newUser = await this.userRepo.update(user.id, {
      username: user.username,
      email: user.email,
      firstName: user.firstName,
      lastName: user.lastName,
      password: user.password,
      roles,
    });
    if (!newUser.affected) {
      throw new Error('update error');
    }
    return this.get(user.id, 'update success');
  }

  public async batchDelete(userIds: number[]): Promise<BaseSingleResponseDto<boolean>> {
    const result = await this.userRepo.delete(userIds);
    if (!result.affected) {
      throw new Error('delete error');
    }
    return {
      success: true,
      message: 'delete success',
      data: true,
    }
  }

  public async findUserAndPwdByName(username: string): Promise<User | null> {
    const user = await this.userRepo.createQueryBuilder('user').select().addSelect('user.password').where({ username }).getOne();
    return user;
  }

  public transformEntity2Dto(entity: User): UserDto {
    return transformResponseDto<UserDto, User>(entity, {
      username: entity.username,
      email: entity.email,
      firstName: entity.firstName,
      lastName: entity.lastName,
      roles: entity.roles?.map(role => (transformResponseDto<RoleDto, Role>(role, {
        name: role.name,
        description: role.description,
      }))),
    });
  }
}
